package com.ic.es.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.ic.domain.FaceDto;
import com.ic.domain.FaceRecognitionDTO;
import com.ic.es.result.PageResult;
import com.ic.es.service.EsFaceService;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 服务层：负责处理人脸数据索引的REST API请求。
 *
 * @author 邹广烨
 * @version 1.0
 * @since 2024-7-18
 */
@Service
public class EsFaceServiceImpl implements EsFaceService {

    private final RestHighLevelClient client;

    private static final String INDEX_PREFIX = "indexface-202408";
    private static final String ALIAS_NAME = "Facial-data";

    /**
     * 使用构造函数注入服务层实例。
     *
     * @param client Elasticsearch的RestHighLevelClient
     */
    @Autowired
    public EsFaceServiceImpl(RestHighLevelClient client) {
        this.client = client;
    }

    /**
     * 接收POST请求，用于索引人脸数据。
     * <p>
     * 该端点用于接收从客户端发送的人脸数据，并将其索引到Elasticsearch中。
     * 客户端应发送一个JSON格式的Body，其中包含必要的人脸数据信息。
     *
     * @param
     */
    @Override
    public void indexFaceData(FaceRecognitionDTO faceRecognitionDTO) {
        try {
            // 创建一个Map来存储你想要索引的字段
            Map<String, Object> jsonMap = new HashMap<>();
//            String indexName = "indexface-" + LocalDate.now().toString().replace("-", "");
//            TODO 还差了设备信息 和 地址ip等新鲜未添加
            String indexName = "indexface-202408";
            jsonMap.put("id", faceRecognitionDTO.getId());
            jsonMap.put("image", faceRecognitionDTO.getImage());
            jsonMap.put("minage", faceRecognitionDTO.getMinage());
            jsonMap.put("maxage", faceRecognitionDTO.getMaxage());
            jsonMap.put("gender", faceRecognitionDTO.getGender());
            jsonMap.put("mask", faceRecognitionDTO.getMask());
            jsonMap.put("date", faceRecognitionDTO.getDate());
            IndexRequest request = new IndexRequest(indexName)
                    .source(jsonMap, XContentType.JSON);
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 接收POST请求，用于索引人脸数据。
     *
     * @param faceDto 包含人脸数据的DTO
     * @return 包含分页结果的PageResult对象
     */
    @Override
    public PageResult<FaceRecognitionDTO> getAllFaceData(FaceDto faceDto) {
        long total = 0;
        List<FaceRecognitionDTO> facesList = new ArrayList<>();
        try {
            SearchRequest searchRequest = new SearchRequest(INDEX_PREFIX);
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

            // 添加时间范围查询
            if (faceDto.getStartDate() != null || faceDto.getEndDate() != null) {
                RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("date");
                if (faceDto.getStartDate() != null) {
                    rangeQuery.gte(faceDto.getStartDate());
                }
                if (faceDto.getEndDate() != null) {
                    rangeQuery.lte(faceDto.getEndDate());
                }
                boolQueryBuilder.filter(rangeQuery);
            }

            searchSourceBuilder.query(QueryBuilders.matchAllQuery());
            searchSourceBuilder.from((faceDto.getPageNum() - 1) * faceDto.getPageSize());
            searchSourceBuilder.size(faceDto.getPageSize());
            searchRequest.source(searchSourceBuilder);

            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = searchResponse.getHits();

            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                FaceRecognitionDTO face = JSONObject.parseObject(sourceAsString, FaceRecognitionDTO.class);
                facesList.add(face);
            }

            total = hits.getTotalHits().value;
        } catch (IOException e) {
            e.printStackTrace();
            // 处理异常，例如记录日志或向客户端返回错误信息
        }

        return PageResult.toPageResult(total, facesList);
    }

    /**
     * 创建一个新索引并更新别名以指向它。
     * 注意：这个方法通常不会在每次查询时调用，而是根据特定的需求（如每月创建一个新索引）来调用。
     */
    public void createNewIndexAndUpdateAlias() {
        String newIndexName = INDEX_PREFIX + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
        try {
            // 创建新索引（如果索引已存在，则忽略错误）
            CreateIndexRequest createIndexRequest = new CreateIndexRequest(newIndexName);
            // 这里可以添加mappings, settings等配置
            client.indices().create(createIndexRequest, RequestOptions.DEFAULT); // 注意：这里不检查isAcknowledged，因为索引可能已存在

            // 更新别名以指向新索引
            IndicesAliasesRequest aliasesRequest = new IndicesAliasesRequest();
            aliasesRequest.addAliasAction(IndicesAliasesRequest.AliasActions.add()
                    .index(newIndexName)
                    .alias(ALIAS_NAME));

            // 注意：这里没有移除旧索引的别名，因为别名始终指向最新的索引
            client.indices().updateAliases(aliasesRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            // 抛出包含中文错误信息的运行时异常
            throw new RuntimeException("创建新索引或更新别名失败", e);
        }
    }


    /**
     * 定时任务，每月执行一次，创建新索引。
     */
    @Scheduled(cron = "0 0 0 1 * ?") // 每月的第一天凌晨0点执行
    public void scheduleCreateNewIndex() {
        createNewIndexAndUpdateAlias();
    }



    /**
     * 获取当前别名指向的索引名称，如果不存在则抛出异常。
     *
     * @return 当前别名指向的索引名称
     * @throws RuntimeException 如果别名没有关联的索引
     */
    private String getCurrentIndexName() {
        try {
            GetAliasesRequest getAliasesRequest = new GetAliasesRequest(ALIAS_NAME);
            GetAliasesResponse getAliasesResponse = client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT);

            if (!getAliasesResponse.getAliases().isEmpty()) {
                Set<String> indices = getAliasesResponse.getAliases().keySet();
                if (!indices.isEmpty()) {
                    return indices.iterator().next();
                }
            }

            // 抛出包含中文错误信息的运行时异常
            throw new RuntimeException("未找到别名对应的索引：" + ALIAS_NAME);
        } catch (IOException e) {
            // 抛出包含中文错误信息的运行时异常
            throw new RuntimeException("从别名获取当前索引名称时发生错误", e);
        }
    }

    /**
     * 获取当前索引名称，格式为"indexface-yyyyMM"，例如"indexface-202408"。
     *
     * @return 当前索引名称
     */
    private String getIndexName() {
        try {
            GetAliasesRequest getAliasesRequest = new GetAliasesRequest(ALIAS_NAME);
            GetAliasesResponse getAliasesResponse = client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT);

            if (!getAliasesResponse.getAliases().isEmpty()) {
                Set<String> indices = getAliasesResponse.getAliases().keySet();
                if (!indices.isEmpty()) {
                    return indices.iterator().next();
                }
            }

            throw new RuntimeException("No index found for alias: " + ALIAS_NAME);
        } catch (IOException e) {
            throw new RuntimeException("Error getting current index name from alias", e);
        }
    }
}
